package com.nervenets.general.utils;

import com.nervenets.general.hibernate.DomainObject;
import com.nervenets.general.service.BaseService;
import com.nervenets.general.service.impl.BaseServiceImpl;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

import static com.nervenets.general.i18n.I18nTranslator.braceTranslate;

public final class CodeTemplateUtil {
    /**
     * 代码生成工具，自动分析生成Dao,Service,Controller及Params
     */
    public static void generateDaoAndServiceAndController(Config config) throws IOException {
        if (!ClassUtils.isInherited(config.getClz(), DomainObject.class)) {
            throw new IllegalArgumentException("生成代码的类必须继承于DomainObject");
        }
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);

        HashMap<String, Object> map = new HashMap<>();
        map.put("package", config.getBasePackage());
        map.put("classPackage", config.getClz().getName());
        final String simpleName = config.getClz().getSimpleName();
        map.put("className", simpleName);
        map.put("apiName", config.getApiName());
        final String apiPath = StringUtils.camelToCharacterSeparation(simpleName, "/");
        map.put("apiPath", apiPath);
        String apiGroup = apiPath.split("/")[0];
        map.put("apiGroup", apiGroup);

        final Field[] declaredFields = config.getClz().getDeclaredFields();
        HashSet<String> dependents = new HashSet<>();
        List<FormField> fields = new ArrayList<>();
        for (Field field : declaredFields) {
            String detail = null;
            ApiModelProperty property = field.getAnnotation(ApiModelProperty.class);
            if (null != property) detail = braceTranslate(property.value());
            String typeName = field.getType().getName();

            final Type genericType = field.getGenericType();
            if (genericType instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) genericType;
                Type[] typeArgs = parameterizedType.getActualTypeArguments();
                final Type rawType = parameterizedType.getRawType();
                List<String> argTypeNames = new ArrayList<>();
                for (Type type : typeArgs) {
                    dependents.add(type.getTypeName());
                    argTypeNames.add(ClassUtils.getClassRealName(type.getTypeName()));
                }
                dependents.add(rawType.getTypeName());
                typeName = String.format("%s<%s>", ClassUtils.getClassRealName(rawType.getTypeName()), String.join(", ", argTypeNames));
            } else {
                if (typeName.contains(".")) {
                    dependents.add(typeName);
                    typeName = ClassUtils.getClassRealName(typeName);
                }
            }

            fields.add(FormField.builder()
                    .detail(detail)
                    .name(field.getName())
                    .type(typeName)
                    .build());
        }
        map.put("fields", fields);
        map.put("dependents", dependents);
        map.put("baseServiceName", config.getBaseService().getSimpleName());
        map.put("baseServicePath", config.getBaseService().getName());
        map.put("baseServiceImplName", config.getBaseServiceImpl().getSimpleName());
        map.put("baseServiceImplPath", config.getBaseServiceImpl().getName());

        VelocityContext context = new VelocityContext(map);

        final String path = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        //final String userDir = System.getProperty("user.dir");
        final String userDir = path.substring(0, path.indexOf("target"));//找到根目录
        final String packagePath = config.getBasePackage().replaceAll("\\.", "/");
        File daoDir = new File(String.format("%s/src/main/java/%s/hibernate/dao/%s", userDir, packagePath, apiGroup));
        File serviceDir = new File(String.format("%s/src/main/java/%s/service/%s", userDir, packagePath, apiGroup));
        File serviceImplDir = new File(String.format("%s/src/main/java/%s/service/%s/impl", userDir, packagePath, apiGroup));
        File controllerDir = new File(String.format("%s/src/main/java/%s/controller/%s", userDir, packagePath, apiGroup));
        File controllerParamsDir = new File(String.format("%s/src/main/java/%s/controller/%s/params", userDir, packagePath, apiGroup));

        //生成DAO.java
        StringWriter daoContent = new StringWriter();
        Template daoTemplate = Velocity.getTemplate("template/Dao.java.vm", "UTF-8");
        daoTemplate.merge(context, daoContent);
        final File daoFile = new File(daoDir, String.format("%sDao.java", simpleName));
        if (daoFile.exists()) daoFile.delete();
        FileUtil.write(daoFile, daoContent.toString());
        System.out.println(daoFile.getAbsolutePath());
        //生成Service.java
        StringWriter serviceContent = new StringWriter();
        Template serviceTemplate = Velocity.getTemplate("template/Service.java.vm", "UTF-8");
        serviceTemplate.merge(context, serviceContent);
        final File serviceFile = new File(serviceDir, String.format("%sService.java", simpleName));
        if (serviceFile.exists()) serviceFile.delete();
        FileUtil.write(serviceFile, serviceContent.toString());
        System.out.println(serviceFile.getAbsolutePath());
        //生成ServiceImpl.java
        StringWriter serviceImplContent = new StringWriter();
        Template serviceImplTemplate = Velocity.getTemplate("template/ServiceImpl.java.vm", "UTF-8");
        serviceImplTemplate.merge(context, serviceImplContent);
        final File serviceImplFile = new File(serviceImplDir, String.format("%sServiceImpl.java", simpleName));
        if (serviceImplFile.exists()) serviceImplFile.delete();
        FileUtil.write(serviceImplFile, serviceImplContent.toString());
        System.out.println(serviceImplFile.getAbsolutePath());
        //生成FormParams.java
        StringWriter formParamsContent = new StringWriter();
        Template formParamsTemplate = Velocity.getTemplate("template/FormParams.java.vm", "UTF-8");
        formParamsTemplate.merge(context, formParamsContent);
        final File formParamsFile = new File(controllerParamsDir, String.format("%sFormParams.java", simpleName));
        if (formParamsFile.exists()) formParamsFile.delete();
        FileUtil.write(formParamsFile, formParamsContent.toString());
        System.out.println(formParamsFile.getAbsolutePath());
        //生成PagingParams.java
        StringWriter pagingParamsContent = new StringWriter();
        Template pagingParamsTemplate = Velocity.getTemplate("template/PagingParams.java.vm", "UTF-8");
        pagingParamsTemplate.merge(context, pagingParamsContent);
        final File pagingParamsFile = new File(controllerParamsDir, String.format("%sPagingParams.java", simpleName));
        if (pagingParamsFile.exists()) pagingParamsFile.delete();
        FileUtil.write(pagingParamsFile, pagingParamsContent.toString());
        System.out.println(pagingParamsFile.getAbsolutePath());
        //生成Controller.java
        StringWriter controllerContent = new StringWriter();
        Template controllerTemplate = Velocity.getTemplate("template/Controller.java.vm", "UTF-8");
        controllerTemplate.merge(context, controllerContent);
        final File controllerFile = new File(controllerDir, String.format("%sController.java", simpleName));
        if (controllerFile.exists()) controllerFile.delete();
        FileUtil.write(controllerFile, controllerContent.toString());
        System.out.println(controllerFile.getAbsolutePath());
    }

    @Setter
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    @SuperBuilder
    public static class Config {
        @ApiModelProperty(value = "基础包")
        private String basePackage;
        @ApiModelProperty(value = "类")
        private Class<?> clz;
        @ApiModelProperty(value = "接口名称")
        private String apiName;
        @ApiModelProperty(value = "基础Service")
        @Builder.Default
        private Class<?> baseService = BaseService.class;
        @ApiModelProperty(value = "基础Service实现")
        @Builder.Default
        private Class<?> baseServiceImpl = BaseServiceImpl.class;
    }

    @Setter
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    @SuperBuilder
    public static class FormField {
        @ApiModelProperty(value = "描述")
        private String detail;
        @ApiModelProperty(value = "名称")
        private String name;
        @ApiModelProperty(value = "类型")
        private String type;
    }

}
